#include "Rp2ObjLoader.h"
#include "Rp2TextureEffect.h"
#include "Rp2LightingMaterialEffect.h"
// Disable Microsoft warning about unsafe functions (security).
#pragma warning(disable:4996)

using namespace Rp2;

//---------------------------------------------------------------------------------------------------
ObjLoader::ObjLoader(const std::string& filename)
	:
	mCode(EC_SUCCESSFUL),
    mCurrentGroup(-1),
    mCurrentPos(-1),
    mCurrentTcd(-1),
    mCurrentNor(-1),
    mCurrentMtl(-1),
    mCurrentMesh(-1)
{
	mpkMoMesh = new ModelMesh;
}
//---------------------------------------------------------------------------------------------------
ObjLoader::~ObjLoader()
{
}
//---------------------------------------------------------------------------------------------------
std::vector<TriMesh*>* ObjLoader::LoadObj(const std::string& filename, std::vector<TriMesh*>& akMesh)
{	
	ObjLoader* pkLoader = new ObjLoader(filename);

	pkLoader->mLogFile = fopen("ObjLogFile.txt", "wt");
	if (!pkLoader->mLogFile)
	{
        assert(false);
        pkLoader->mCode = EC_LOGFILE_OPEN_FAILED;
        return 0;
	}
	
	const char* filePath = System::GetPath(filename.c_str(), System::SM_READ);
	std::ifstream inFile(filePath);
    if (!inFile)
    {
        assert(false);
        pkLoader->mCode = EC_FILE_OPEN_FAILED;
        fprintf(pkLoader->mLogFile, "%s: %s\n", pkLoader->msCodeString[pkLoader->mCode], filePath);
        fclose(pkLoader->mLogFile);
        return 0;
    }

    std::string line;
    std::vector<std::string> tokens;

    while (!inFile.eof())
    {
        getline(inFile, line);

        // Skip blank lines.
        if (line == "") { continue; }

        // Skip comments.
        if (line[0] == '#') { continue; }

        pkLoader->GetTokens(line, tokens);
        if (tokens.size() == 0)
        {
            assert(false);
            pkLoader->mCode = EC_NO_TOKENS;
            break;
        }

        // mtllib
        if (pkLoader->GetMaterialLibrary(tokens)) { continue; }
        if (pkLoader->mCode != EC_SUCCESSFUL) { break; }

        // g default
        if (pkLoader->GetDefaultGroup(tokens)) { continue; }
        if (pkLoader->mCode != EC_SUCCESSFUL) { break; }

        // v x y z
        if (pkLoader->GetPosition(tokens)) { continue; }
        if (pkLoader->mCode != EC_SUCCESSFUL) { break; }

        // vt x y
        if (pkLoader->GetTCoord(tokens)) { continue; }
        if (pkLoader->mCode != EC_SUCCESSFUL) { break; }

        // vn x y z
        if (pkLoader->GetNormal(tokens)) { continue; }
        if (pkLoader->mCode != EC_SUCCESSFUL) { break; }

        // Ignore smoothing groups for now (syntax:  's number').
        if (tokens[0] == "s") { continue; }

        // g groupname
        if (pkLoader->GetGroup(tokens)) { continue; }
        if (pkLoader->mCode != EC_SUCCESSFUL) { break; }

        // usemtl mtlname
        if (pkLoader->GetMaterialAndMesh(tokens)) { continue; }
        if (pkLoader->mCode != EC_SUCCESSFUL) { break; }

        // f vertexList
        if (pkLoader->GetFace(tokens)) { continue; }
        if (pkLoader->mCode != EC_SUCCESSFUL) { break; }

        assert(false);
        pkLoader->mCode = EC_UNEXPECTED_TOKEN;
        break;
    }

    if (pkLoader->mCode != EC_SUCCESSFUL)
    {
        fprintf(pkLoader->mLogFile, "%s: %s\n", msCodeString[pkLoader->mCode], line.c_str());
    }
    else
    {
        fprintf(pkLoader->mLogFile, "%s\n", msCodeString[EC_SUCCESSFUL]);
    }
    fclose(pkLoader->mLogFile);
    inFile.close();

	if (pkLoader->mCode == EC_SUCCESSFUL)
	{
		// Save data to native model mesh
		Attributes kAttr;
		kAttr.SetPositionChannels(3);
		kAttr.SetNormalChannels(3);
		kAttr.SetTCoordChannels(0, 2);

		int iIndexQuantity = pkLoader->mVertices.size();
		int iGroupQuantity = pkLoader->mGroups.size();
		int i, iSave, iTmp = 0, iGroupIndex = 0;
		for (; iGroupIndex < iGroupQuantity; iGroupIndex++)
		{
			iSave = iTmp;
			iTmp = iIndexQuantity;
			if (iGroupIndex + 1 < iGroupQuantity)
			{	
				std::vector<Face>* kF;
				Group* kG = &pkLoader->mGroups[iGroupIndex];
				std::vector<Mesh>* kM = &(*kG).Meshes;
				for (int j = 0; j < (int)kM->size(); j++)	// get meshes quantity
				{
					kF = &(*kM)[j].Faces; // get faces quantity
				}
				iTmp = kF->size()*3 + iSave; // get group offset
			}
			
			TriMesh* pkMesh;
			VertexBuffer* pkVB = new VertexBuffer(kAttr, iTmp - iSave);
			IndexBuffer* pkIB = new IndexBuffer(iTmp - iSave);
			int* aiIndex = pkIB->GetData();
			for (i = 0; i < iTmp - iSave; i += 3)
			{
				// index 0
				aiIndex[i] = pkLoader->mVertices[i+2+iSave].PosIndex - iSave;
				pkVB->Position3(aiIndex[i]) = pkLoader->mPositions[aiIndex[i] + iSave];
				pkVB->Normal3(aiIndex[i]) =
					pkLoader->mNormals[pkLoader->mVertices[i+2+iSave].NorIndex];
				pkVB->TCoord2(0,aiIndex[i]) =
					pkLoader->mTCoords[pkLoader->mVertices[i+2+iSave].TcdIndex];
			
				// index 1
				int j = i + 1;
				aiIndex[j] = pkLoader->mVertices[j+iSave].PosIndex - iSave;
				pkVB->Position3(aiIndex[j]) = pkLoader->mPositions[aiIndex[j] + iSave];
				pkVB->Normal3(aiIndex[j]) =
					pkLoader->mNormals[pkLoader->mVertices[j+iSave].NorIndex];
				pkVB->TCoord2(0,aiIndex[j]) =
					pkLoader->mTCoords[pkLoader->mVertices[j+iSave].TcdIndex];

				// index 2
				j = i + 2;
				aiIndex[j] = pkLoader->mVertices[i+iSave].PosIndex - iSave;
				pkVB->Position3(aiIndex[j]) = pkLoader->mPositions[aiIndex[j] + iSave];
				pkVB->Normal3(aiIndex[j]) =
					pkLoader->mNormals[pkLoader->mVertices[i+iSave].NorIndex];
				pkVB->TCoord2(0,aiIndex[j]) =
					pkLoader->mTCoords[pkLoader->mVertices[i+iSave].TcdIndex];
			}
			pkMesh = new TriMesh(pkVB, pkIB);

			// Get texture
			Group& kCurrGroup = pkLoader->mGroups[iGroupIndex];
			int iMtlIndex = kCurrGroup.Meshes[0].MtlIndex;
			std::string& kTexName = pkLoader->mMaterials[iMtlIndex].DiffuseMap.Filename;
			const char* acTexPath = System::GetPath(kTexName.c_str(), System::SM_READ);
			assert(acTexPath);
			pkMesh->AttachEffect(new LightingMaterialEffect(std::string(acTexPath)));

			// attach material state
			MaterialState* pkMatState = new MaterialState;
			pkMatState->Diffuse = ColorRGB(1.0f, 1.0f, 1.0f);
			pkMatState->Specular = ColorRGB(1.0f, 1.0f, 1.0f);
			pkMatState->Alpha = 1.0f;
			pkMatState->Shininess = 1;
			pkMesh->AttachGlobalState(pkMatState);

			akMesh.push_back(pkMesh);

			// for debug
// 			if (iGroupIndex == 1)
// 			{
// 				pkVB->PrintToFile("VBuffer.txt");
// 				pkIB->PrintToFile("IBuffer.txt");
// 			}

		}
	}
	return &akMesh;
}
//---------------------------------------------------------------------------------------------------
void ObjLoader::GetTokens (const std::string & line, std::vector<std::string>& tokens)
{
    tokens.clear();

    std::string::size_type begin, end = 0;
    std::string token;

    while ((begin = line.find_first_not_of(" \t", end)) != std::string::npos)
    {
        end = line.find_first_of(" \t", begin);
        token = line.substr(begin, end-begin);
        tokens.push_back(token);
    }
}
//---------------------------------------------------------------------------------------------------
bool ObjLoader::GetMaterialLibrary (const std::vector<std::string>& tokens)
{
    if (tokens[0] == "mtllib")
    {
        if (tokens.size() == 1)
        {
            assert(false);
            mCode = EC_TOO_FEW_TOKENS;
            return false;
        }
        if (tokens.size() > 2)
        {
            assert(false);
            mCode = EC_TOO_MANY_TOKENS;
            return false;
        }

        MtlLoader loader(tokens[1]);
        if (loader.GetCode() != MtlLoader::EC_SUCCESSFUL)
        {
            assert(false);
            mCode = EC_FAILED_TO_LOAD_MATERIALS;
            return false;
        }

        mMaterials = loader.GetMaterials();
        return true;
    }
    return false;
}
//---------------------------------------------------------------------------------------------------
bool ObjLoader::GetDefaultGroup (const std::vector<std::string>& tokens)
{
    if (tokens[0] == "g")
    {
        if (tokens.size() == 1)
        {
            assert(false);
            mCode = EC_TOO_FEW_TOKENS;
            return false;
        }
        if (tokens.size() > 2)
        {
            assert(false);
            mCode = EC_TOO_MANY_TOKENS;
            return false;
        }

        if (tokens[1] == "default")
        {
            mCurrentPos = (int)mPositions.size();
            mCurrentTcd = (int)mTCoords.size();
            mCurrentNor = (int)mNormals.size();
            return true;
        }
    }
    return false;
}
//---------------------------------------------------------------------------------------------------
bool ObjLoader::GetPosition (const std::vector<std::string>& tokens)
{
    if (tokens[0] == "v")
    {
        if (tokens.size() < 4)
        {
            assert(false);
            mCode = EC_TOO_FEW_TOKENS;
            return false;
        }
        if (tokens.size() > 4)
        {
            assert(false);
            mCode = EC_TOO_MANY_TOKENS;
            return false;
        }

		Vector3f pos;
        pos.X() = (float)atof(tokens[1].c_str());
        pos.Y() = (float)atof(tokens[2].c_str());
        pos.Z() = (float)atof(tokens[3].c_str());
        mPositions.push_back(pos);
        return true;
    }
    return false;
}
//---------------------------------------------------------------------------------------------------
bool ObjLoader::GetTCoord (const std::vector<std::string>& tokens)
{
    if (tokens[0] == "vt")
    {
        if (tokens.size() < 3)
        {
            assert(false);
            mCode = EC_TOO_FEW_TOKENS;
            return false;
        }
        if (tokens.size() > 3)
        {
            // TODO.  Need to handle 3D texture coordinates.
            assert(false);
            mCode = EC_TOO_MANY_TOKENS;
            return false;
        }

        Vector2f tcd;
        tcd.X() = (float)atof(tokens[1].c_str());
        tcd.Y() = (float)atof(tokens[2].c_str());
        mTCoords.push_back(tcd);
        return true;
    }
    return false;
}
//---------------------------------------------------------------------------------------------------
bool ObjLoader::GetNormal (const std::vector<std::string>& tokens)
{
    if (tokens[0] == "vn")
    {
        if (tokens.size() < 4)
        {
            assert(false);
            mCode = EC_TOO_FEW_TOKENS;
            return false;
        }
        if (tokens.size() > 4)
        {
            assert(false);
            mCode = EC_TOO_MANY_TOKENS;
            return false;
        }

		Vector3f nor;
        nor.X() = (float)atof(tokens[1].c_str());
        nor.Y() = (float)atof(tokens[2].c_str());
        nor.Z() = (float)atof(tokens[3].c_str());
        mNormals.push_back(nor);
        return true;
    }
    return false;
}
//---------------------------------------------------------------------------------------------------
bool ObjLoader::GetGroup (const std::vector<std::string>& tokens)
{
    if (tokens[0] == "g")
    {
        if (tokens.size() == 1)
        {
            assert(false);
            mCode = EC_TOO_FEW_TOKENS;
            return false;
        }

        if (tokens[1] == "default")
        {
            assert(false);
            mCode = EC_UNEXPECTED_TOKEN;
            return false;
        }

        mCurrentGroup = (int)mGroups.size();
        mGroups.push_back(Group());
        Group& group = mGroups[mCurrentGroup];
        group.Name = tokens[1];
        for (int i = 2; i < (int)tokens.size(); ++i)
        {
            group.Name += tokens[i];
        }
        group.PosStart = mCurrentPos;
        group.TcdStart = mCurrentTcd;
        group.NorStart = mCurrentNor;
        mCurrentPos = (int)mPositions.size();
        mCurrentTcd = (int)mTCoords.size();
        mCurrentNor = (int)mNormals.size();
        return true;
    }
    return false;
}
//---------------------------------------------------------------------------------------------------
bool ObjLoader::GetMaterialAndMesh (const std::vector<std::string>& tokens)
{
    if (tokens[0] == "usemtl")
    {
        if (tokens.size() == 1)
        {
            assert(false);
            mCode = EC_TOO_FEW_TOKENS;
            return false;
        }
        if (tokens.size() > 2)
        {
            assert(false);
            mCode = EC_TOO_MANY_TOKENS;
            return false;
        }

        int i;
        for (i = 0; i < (int)mMaterials.size(); ++i)
        {
            if (tokens[1] == mMaterials[i].Name)
            {
                break;
            }
        }
        if (i == (int)mMaterials.size())
        {
            assert(false);
            mCode = EC_FAILED_TO_FIND_MATERIAL;
            return false;
        }
        mCurrentMtl = i;

        Group& group = mGroups[mCurrentGroup];
        for (i = 0; i < (int)group.Meshes.size(); ++i)
        {
            if (group.Meshes[i].MtlIndex == mCurrentMtl)
            {
                break;
            }
        }
        if (i == (int)group.Meshes.size())
        {
            // Mesh with this material does not yet exist.
            group.Meshes.push_back(Mesh());
            group.Meshes.back().MtlIndex = mCurrentMtl;
        }

        mCurrentMesh = i;
        return true;
    }
    return false;
}
//---------------------------------------------------------------------------------------------------
bool ObjLoader::GetFace (const std::vector<std::string>& tokens)
{
    if (tokens[0] == "f")
    {
        if (tokens.size() < 4)
        {
            // A face must have at least three vertices.
            assert(false);
            mCode = EC_TOO_FEW_TOKENS;
            return false;
        }

		if (tokens.size() > 4)
		{
			// The face is not a triangle, which is not
			// supported by the engine now
			assert(false);
			mCode = EC_TOO_MANY_TOKENS;
			return false;
		}

        Group& group = mGroups[mCurrentGroup];
        Mesh& mesh = group.Meshes[mCurrentMesh];
        mesh.Faces.push_back(Face());
        Face& face = mesh.Faces.back();

        // A vertex is one of the following.
        // v/vt/vn
        // v/vt/
        // v//vn
        // v//
        const int numVertices = (int)tokens.size() - 1;
        face.Vertices.resize(numVertices);
        for (int i = 0; i < numVertices; ++i)
        {
            std::string token = tokens[i+1];
            std::string::size_type slash1 = token.find_first_of("/");
            std::string::size_type slash2 = token.find_last_of("/");
            if (slash1 == 0 || slash1 == std::string::npos
            ||  slash2 == 0 || slash2 == std::string::npos
            ||  slash1 == slash2)
            {
                assert(false);
                mCode = EC_INVALID_VERTEX;
                return false;
            }

            std::string pos = token.substr(0, slash1);
            face.Vertices[i].PosIndex = atoi(pos.c_str()) - 1;
            int numDigits = slash2 - slash1 - 1;
            if (numDigits > 0)
            {
                std::string tcd = token.substr(slash1 + 1, numDigits);
                face.Vertices[i].TcdIndex = atoi(tcd.c_str()) - 1;
            }
            if (token.length() > slash2 + 1)
            {
                std::string nor = token.substr(slash2 + 1, std::string::npos);
                face.Vertices[i].NorIndex = atoi(nor.c_str()) - 1;
            }
			// Vertices container
			mVertices.push_back(face.Vertices[i]);
        }
        return true;
    }
    return false;
}
//---------------------------------------------------------------------------------------------------